package qualomelhor.game;

import org.jbox2d.callbacks.ContactImpulse;
import org.jbox2d.callbacks.ContactListener;
import org.jbox2d.callbacks.DebugDraw;
import org.jbox2d.collision.Manifold;
import org.jbox2d.collision.shapes.PolygonShape;
import org.jbox2d.common.Vec2;
import org.jbox2d.dynamics.Body;
import org.jbox2d.dynamics.BodyDef;
import org.jbox2d.dynamics.World;
import org.jbox2d.dynamics.contacts.Contact;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Stack;
import static playn.core.PlayN.graphics;
import playn.core.CanvasImage;
import playn.core.CanvasLayer;
import playn.core.DebugDrawBox2D;
import playn.core.GroupLayer;
import playn.core.ImageLayer;
import qualomelhor.game.entities.Entity;
import qualomelhor.game.entities.NewDynamicPhysicsEntity;
import qualomelhor.game.entities.PhysicsEntity;

public class MyWorld implements ContactListener {
	public GroupLayer staticLayerBack;
	public GroupLayer dynamicLayer;
	public GroupLayer staticLayerFront;

	// size of world
	private static int width = 600;
	private static int height = 300;

	// box2d object containing physics world
	protected World world;

	private List<Entity> entities = new ArrayList<Entity>(0);
	private List<NewDynamicPhysicsEntity> newEntities = new ArrayList<NewDynamicPhysicsEntity>(0);
	private HashMap<Body, PhysicsEntity> bodyEntityLUT = new HashMap<Body, PhysicsEntity>();
	private Stack<Contact> contacts = new Stack<Contact>();

	private static boolean showDebugDraw = true;
	private DebugDrawBox2D debugDraw;
	private float physUnitPerScreenUnit;

	public MyWorld(GroupLayer scaledLayer, float phy) {
		physUnitPerScreenUnit = phy;
		staticLayerBack = graphics().createGroupLayer();
		scaledLayer.add(staticLayerBack);
		dynamicLayer = graphics().createGroupLayer();
		scaledLayer.add(dynamicLayer);
		staticLayerFront = graphics().createGroupLayer();
		scaledLayer.add(staticLayerFront);

		// create the physics world
		Vec2 gravity = new Vec2(0.0f, 10.0f);
		world = new World(gravity, true);
		world.setWarmStarting(true);
		world.setAutoClearForces(true);
		world.setContactListener(this);

		// create the ground
		Body ground = world.createBody(new BodyDef());
		PolygonShape groundShape = new PolygonShape();
		groundShape.setAsEdge(new Vec2(0, height * physUnitPerScreenUnit), new Vec2(width * physUnitPerScreenUnit, height * physUnitPerScreenUnit));
		ground.createFixture(groundShape, 0.0f);

		// create the walls
		Body wallLeft = world.createBody(new BodyDef());
		PolygonShape wallLeftShape = new PolygonShape();
		wallLeftShape.setAsEdge(new Vec2(0, 0), new Vec2(0, height * physUnitPerScreenUnit));
		wallLeft.createFixture(wallLeftShape, 0.0f);
		Body wallRight = world.createBody(new BodyDef());
		PolygonShape wallRightShape = new PolygonShape();
		wallRightShape.setAsEdge(new Vec2(width * physUnitPerScreenUnit, 0), new Vec2(width * physUnitPerScreenUnit, height * physUnitPerScreenUnit));
		wallRight.createFixture(wallRightShape, 0.0f);

		if (showDebugDraw) {
			CanvasImage canvasimg = graphics().createImage((int) (width / physUnitPerScreenUnit),
              (int) (height / physUnitPerScreenUnit));
			ImageLayer canvasLayer = graphics().createImageLayer(canvasimg);
			//CanvasLayer canvasLayer =	graphics().createCanvasLayer((int) (width / PeasDemo.physUnitPerScreenUnit), (int) (height / PeasDemo.physUnitPerScreenUnit));
			graphics().rootLayer().add(canvasLayer);
			debugDraw = new DebugDrawBox2D();
			debugDraw.setCanvas(canvasimg);
			debugDraw.setFlipY(false);
			debugDraw.setStrokeAlpha(150);
			debugDraw.setFillAlpha(75);
			debugDraw.setStrokeWidth(2.0f);
			debugDraw.setFlags(DebugDraw.e_shapeBit | DebugDraw.e_jointBit | DebugDraw.e_aabbBit);
			debugDraw.setCamera(0, 0, 1f / physUnitPerScreenUnit);
			world.setDebugDraw(debugDraw);
		}
	}

	public void update(float delta) {
		for (Entity e : entities) {
			e.update(delta);
		}
		
		for (NewDynamicPhysicsEntity ne : newEntities) {
			ne.update(delta);
		}
		
		// the step delta is fixed so box2d isn't affected by framerate
		world.step(0.033f, 10, 10);
		processContacts();
	}

	public void paint(float delta) {
		if (showDebugDraw) {
			debugDraw.getCanvas().clear();
			world.drawDebugData();
		}
		for (Entity e : entities) {
			e.paint(delta);
		}

		for (NewDynamicPhysicsEntity ne : newEntities) {
			ne.paint(delta);
		}
	}

	public void add(Entity entity) {
		entities.add(entity);
		if (entity instanceof PhysicsEntity) {
			PhysicsEntity physicsEntity = (PhysicsEntity) entity;
			bodyEntityLUT.put(physicsEntity.getBody(), physicsEntity);
		}		
	}

	public void add(NewDynamicPhysicsEntity newEntity) {
		newEntities.add(newEntity);
		if (newEntity instanceof PhysicsEntity) {
			PhysicsEntity physicsEntity = (PhysicsEntity) newEntity;
			bodyEntityLUT.put(physicsEntity.getBody(), physicsEntity);
		}		
	}

	// handle contacts out of physics loop
	public void processContacts() {
		while (!contacts.isEmpty()) {
			Contact contact = contacts.pop();

			// handle collision
			PhysicsEntity entityA = bodyEntityLUT.get(contact.m_fixtureA.m_body);
			PhysicsEntity entityB = bodyEntityLUT.get(contact.m_fixtureB.m_body);

			if (entityA != null && entityB != null) {
				if (entityA instanceof PhysicsEntity.HasContactListener) {
					((PhysicsEntity.HasContactListener) entityA).contact(entityB);
				}
				if (entityB instanceof PhysicsEntity.HasContactListener) {
					((PhysicsEntity.HasContactListener) entityB).contact(entityA);
				}
			}
		}
	}

	// Box2d's begin contact
	@Override
	public void beginContact(Contact contact) {
		contacts.push(contact);
	}

	// Box2d's end contact
	@Override
	public void endContact(Contact contact) {
	}

	// Box2d's pre solve
	@Override
	public void preSolve(Contact contact, Manifold oldManifold) {
	}

	// Box2d's post solve
	@Override
	public void postSolve(Contact contact, ContactImpulse impulse) {
	}
}